package com.zxjbyte.yiyi.extension.job.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.cglib.CglibUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.zxjbyte.yiyi.extension.job.core.JobKeyBuilder;
import com.zxjbyte.yiyi.extension.job.core.ObjectJobCreationArgs;
import com.zxjbyte.yiyi.extension.job.core.ObjectJobUpdateArgs;
import com.zxjbyte.yiyi.framework.common.domain.api.PageResult;
import com.zxjbyte.yiyi.framework.common.util.AuthUtil;
import com.zxjbyte.yiyi.framework.common.util.JsonUtil;
import com.zxjbyte.yiyi.framework.common.util.ValidationUtil;
import com.zxjbyte.yiyi.framework.mybatis.service.impl.BaseServiceXImpl;
import com.zxjbyte.yiyi.framework.mybatis.util.Condition;
import com.zxjbyte.yiyi.extension.job.core.alarm.AlarmPayload;
import com.zxjbyte.yiyi.extension.job.enums.JobAlarmTypeEnum;
import com.zxjbyte.yiyi.extension.job.manager.QuartzScheduleManager;
import com.zxjbyte.yiyi.extension.job.core.trigger.route.CronTriggerHelper;
import com.zxjbyte.yiyi.extension.job.util.JobUtil;
import com.zxjbyte.yiyi.extension.job.domain.dto.JobConfigDTO;
import com.zxjbyte.yiyi.extension.job.domain.entity.QuartzJobPO;
import com.zxjbyte.yiyi.extension.job.domain.param.QJobAddParam;
import com.zxjbyte.yiyi.extension.job.domain.param.QJobPageParam;
import com.zxjbyte.yiyi.extension.job.domain.param.QJobUpdateParam;
import com.zxjbyte.yiyi.extension.job.domain.vo.QJobPageVO;
import com.zxjbyte.yiyi.extension.job.enums.QuartzJobStatusEnum;
import com.zxjbyte.yiyi.extension.job.mapper.QuartzJobMapper;
import com.zxjbyte.yiyi.extension.job.service.QuartzJobService;
import org.quartz.CronScheduleBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
import java.util.Objects;

/**
 * Quartz任务在线管理实现类
 *
 * @Author zhangxingjia
 * @Date 2023/8/23 17:35
 * @Version: 1.0
 */
@Service
public class QuartzJobServiceImpl extends BaseServiceXImpl<QuartzJobMapper, QuartzJobPO> implements QuartzJobService {

    @Resource
    private QuartzScheduleManager quartzScheduleManager;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void add(QJobAddParam param) {
        CronScheduleBuilder cronScheduleBuilder = CronTriggerHelper.customCronSchedule(param.getCronExpression());
        String jobHandler = JobUtil.checkJobHandler(param.getJobHandler());
        checkJobConfigParam(param.getJobConfig());
        // 保存
        QuartzJobPO entity = new QuartzJobPO();
        CglibUtil.copy(param, entity);
        entity.setJobGroup(jobGroupNullToDefault(param.getJobGroup()));
        entity.setHandlerClassName(jobHandler);
        entity.setJobConfig(JsonUtil.toJsonAsString(param.getJobConfig()));
        save(entity);
        // 添加定时任务
        executeAddJob(param, cronScheduleBuilder, jobHandler, entity.getId());
    }

    @Override
    public PageResult<QJobPageVO> list(QJobPageParam param) {
        LambdaQueryWrapper<QuartzJobPO> wrapper = Wrappers.<QuartzJobPO>query().checkSqlInjection().lambda().eq(Objects.nonNull(param.getStatus()), QuartzJobPO::getStatus, param.getStatus())
                .like(StrUtil.isNotBlank(param.getJobName()), QuartzJobPO::getJobName, param.getJobName())
                .like(StrUtil.isNotBlank(param.getJobGroup()), QuartzJobPO::getJobGroup, param.getJobGroup())
                .like(StrUtil.isNotBlank(param.getJobHandler()), QuartzJobPO::getHandlerClassName, param.getJobHandler());
        IPage<QuartzJobPO> page = page(Condition.getPage(param), wrapper);
        List<QuartzJobPO> records = page.getRecords();
        if(CollUtil.isNotEmpty(records)){
            List<QJobPageVO> voList = CglibUtil.copyList(records, QJobPageVO::new);
            return PageResult.of(voList, page);
        }
        return PageResult.emptyOf((int)page.getCurrent(), (int)page.getSize());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateJob(QJobUpdateParam param) {
        QuartzJobPO quartzJobPo = getById(param.getId());
        ValidationUtil.assertFalse(Objects.isNull(quartzJobPo), "定时任务不存在！");
        CronScheduleBuilder cronScheduleBuilder = CronTriggerHelper.customCronSchedule(param.getCronExpression());
        String jobHandler = JobUtil.checkJobHandler(param.getJobHandler());
        checkJobConfigParam(param.getJobConfig());
        String oldJobName = quartzJobPo.getJobName();
        String oldJobGroup = quartzJobPo.getJobGroup();
        quartzJobPo.setCronExpression(param.getCronExpression());
        quartzJobPo.setJobName(param.getJobName());
        quartzJobPo.setJobGroup(jobGroupNullToDefault(param.getJobGroup()));
        quartzJobPo.setHandlerClassName(jobHandler);
        quartzJobPo.setHandlerMethodName(StrUtil.nullToEmpty(param.getJobMethodName()));
        quartzJobPo.setMethodParam(StrUtil.nullToEmpty(param.getMethodParam()));
        quartzJobPo.setJobConfig(JsonUtil.toJsonAsString(param.getJobConfig()));
        updateById(quartzJobPo);
        // 执行更新定时任务
        executeUpdateJob(param, cronScheduleBuilder, jobHandler, quartzJobPo.getId(), oldJobName, oldJobGroup);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delete(String name, String group) {
        remove(Wrappers.<QuartzJobPO>lambdaQuery().eq(QuartzJobPO::getJobName, name).eq(QuartzJobPO::getJobGroup, jobGroupNullToDefault(group)));
        quartzScheduleManager.deleteJob(name, group);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void pauseJob(String name, String group) {
        lambdaUpdate().set(QuartzJobPO::getStatus, QuartzJobStatusEnum.PAUSE.getCode())
                .set(QuartzJobPO::getUpdateTime, new Date())
                .set(QuartzJobPO::getUpdateUser, AuthUtil.getUserIdAsStr())
                .eq(QuartzJobPO::getJobName, name).eq(QuartzJobPO::getJobGroup, jobGroupNullToDefault(group))
                .update();
        quartzScheduleManager.pauseJob(name, group);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void resumeJob(String name, String group) {
        lambdaUpdate().set(QuartzJobPO::getStatus, QuartzJobStatusEnum.NORMAL.getCode())
                .set(QuartzJobPO::getUpdateTime, new Date())
                .set(QuartzJobPO::getUpdateUser, AuthUtil.getUserIdAsStr())
                .eq(QuartzJobPO::getJobName, name).eq(QuartzJobPO::getJobGroup, jobGroupNullToDefault(group))
                .update();
        quartzScheduleManager.resumeJob(name, group);
    }

    /**
     * 更新定时任务
     *
     * @param param
     * @param cronScheduleBuilder
     * @param oldJobName
     * @param oldJobGroup
     */
    private void executeUpdateJob(QJobUpdateParam param, CronScheduleBuilder cronScheduleBuilder, String jobHandler,
                                  Long jobId, String oldJobName, String oldJobGroup){
        JobConfigDTO jobConfig = param.getJobConfig();
        quartzScheduleManager.updateJobTrigger(ObjectJobUpdateArgs.newBuilder()
                .oldTriggerKey(oldJobName, oldJobGroup)
                .triggerKey(param.getJobName(), param.getJobGroup())
                .cronExpression(cronScheduleBuilder)
                .jobClassName(jobHandler)
                .methodParam(StrUtil.isNotBlank(param.getMethodParam()), param.getMethodParam())
                .retryInterval(jobConfig.getFailRetryInterval())
                .failRetryCount(jobConfig.getFailRetryCount())
                .jobLog(jobConfig.getNeedJobLog())
                .alarmEmail(jobConfig.getNeedAlarmEmail(), buildAlarmEmailPayload(jobConfig, jobId, param.getCronExpression()))
                .build());
    }

    /**
     * 添加定时任务
     *
     * @param param
     * @param cronScheduleBuilder
     */
    private void executeAddJob(QJobAddParam param, CronScheduleBuilder cronScheduleBuilder, String jobHandler, long jobId){
        JobConfigDTO jobConfig = param.getJobConfig();
        quartzScheduleManager.addJob(ObjectJobCreationArgs.newBuilder()
                .cronExpression(cronScheduleBuilder)
                .jobKey(param.getJobName(), param.getJobGroup())
                .jobClassName(jobHandler)
                .methodParam(StrUtil.isNotBlank(param.getMethodParam()), param.getMethodParam())
                .retryInterval(jobConfig.getFailRetryInterval())
                .failRetryCount(jobConfig.getFailRetryCount())
                .jobLog(jobConfig.getNeedJobLog())
                .alarmEmail(jobConfig.getNeedAlarmEmail(), buildAlarmEmailPayload(jobConfig, jobId, param.getCronExpression()))
                .build());
    }

    /**
     * 构建告警邮件参数
     */
    private AlarmPayload buildAlarmEmailPayload(JobConfigDTO jobConfig, long jobId, String cronExpression){
        return AlarmPayload.of(jobId, cronExpression, jobConfig.getAlarmType(), jobConfig.getTimeout(),
                jobConfig.getAuthor(), jobConfig.getEmailAddress());
    }

    private void checkJobConfigParam(JobConfigDTO dto){
        if(Boolean.TRUE.equals(dto.getNeedAlarmEmail())){
            ValidationUtil.assertFalse(StrUtil.isBlank(dto.getAlarmType()) || !JobAlarmTypeEnum.checkIsExist(dto.getAlarmType()), "请选择正确的触发报警类型！");
        }
    }

    private String jobGroupNullToDefault(String group){
        return StrUtil.isBlank(group) ? JobKeyBuilder.JOB_KEY_DEFAULT_GROUP : group;
    }
}
